package com.woniu.woniu_bx.component;


import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.woniu.woniu_bx.pojo.Chat;
import com.woniu.woniu_bx.pojo.User;
import com.woniu.woniu_bx.service.ChatService;
import com.woniu.woniu_bx.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @ClassName WebSocketServer
 * @Description websocket服务
 * @Author 魏才林
 * @Date 2022/9/6 17:49
 * @Version 1.0
 **/
@Slf4j
@ServerEndpoint(value = "/imserver/{userId}")
@Component
public class WebSocketServer {
    /**
     * 此时的@Autowire注入的对象都为null
     * <p>
     * 产生原因：spring管理的都是单例（singleton），和 websocket （多对象）相冲突。
     * 详细解释：项目启动时初始化，会初始化 websocket （非用户连接的），spring 同时会为其注入 service，
     * 该对象的 service 不是 null，被成功注入。
     * <p>
     * 但是，由于 spring 默认管理的是单例，所以只会注入一次 service。
     * 当客户端与服务器端进行连接时，服务器端又会创建一个新的 websocket 对象，这时问题出现了：spring 管理的都是单例，
     * 不会给第二个 websocket 对象注入 service，所以导致只要是用户连接创建的 websocket 对象，都不能再注入了。
     * 像 controller 里面有 service， service 里面有 mapper。因为 controller，service，mapper 都有是单例，
     * 所以注入时不会报 null，但是由于websocket 不是单例，所以使用spring注入一次后，后面的对象就不会再注入了，会报NullException。
     **/
//    @Autowired
//    private UserService userService;
//
//    @Autowired
//    private ChatService chatService;

    /**
     * 解决方案：把需要注入的对象变为静态static，
     * 让service属于这WebSocketServer类，然后给WebSocketServer类的service写set方法进行注入。
     * 此注入，就可以使得在spring管理下的WebSocketServer类，
     * 在第一次注入的时候，就把service的对象注入，即使后面有多个webSocket时，也能保证其service不为null
     **/
    private static UserService userService;
    private static ChatService chatService;

    @Autowired
    public void setUserService(UserService userService) {
        WebSocketServer.userService = userService;
    }

    @Autowired
    public void setChatService(ChatService chatService) {
        WebSocketServer.chatService = chatService;
    }


    /**
     * 记录当前在线连接数
     */
    public static final Map<String, Session> sessionMap = new ConcurrentHashMap<>();

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("userId") String userId) {
        //将用户名存到map中
        sessionMap.put(userId, session);
        log.debug("有新用户加入，userId={}, 当前在线人数为：{}", userId, sessionMap.size());
        JSONObject result = new JSONObject();
        JSONArray array = new JSONArray();
        result.set("users", array);
        for (Object key : sessionMap.keySet()) {
            JSONObject jsonObject = new JSONObject();
            jsonObject.set("userId", key);

            //单条Json
            // {"userId", "1"}

            array.add(jsonObject);
        }
        //数组
        //{"users": [{"userId": "1"},{ "userId": "2"}]}

        sendAllMessage(JSONUtil.toJsonStr(result));  // 后台发送消息给所有的客户端
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose(Session session, @PathParam("userId") String userId) {
        sessionMap.remove(userId);
        log.debug("有一连接关闭，userId={}的用户session, 当前在线人数为：{}", userId, sessionMap.size());
    }

    /**
     * 收到客户端消息后调用的方法
     * 后台收到客户端发送过来的消息
     * onMessage 是一个消息的中转站
     * 接受 浏览器端 socket.send 发送过来的 json数据
     *
     * @param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, Session session, @PathParam("userId") String userId) {
        log.debug("服务端收到用户userId={}的消息:{}", userId, message);


        //（1）获取前端传来的整个消息对象
        //{"acceptId": "2", "context": "hello"}
        JSONObject obj = JSONUtil.parseObj(message);
        String sendId = userId; // sendId，发送方的ID
        String acceptId = obj.getStr("acceptId"); // acceptId，接受方的ID，这个根据前后端来自定义
        String content = obj.getStr("content"); //   context，发送的内容，这个根据前后端来自定义

        //（2）找到接受方用户的在线的session
        Session acceptSession = sessionMap.get(acceptId); // 根据 acceptId来获取 session，再通过session发送消息文本
        if (acceptSession != null) {
            // 服务器端 再把消息组装一下，组装后的消息包含发送人和发送的文本内容
            // {"sendId": "1","context": "hello"}
            JSONObject jsonObject = new JSONObject();
            jsonObject.set("sendId", sendId);
            jsonObject.set("content", content);


            //将封装好的消息打包成json对象，有服务器主动发送给接受方，前端接受方通过uni.onSocketMessage来接受消息
            this.sendMessage(jsonObject.toString(), acceptSession);

            //将发送的消息存到数据库中
            Chat chat = new Chat();
            chat.setSendId(Integer.parseInt(sendId));
            chat.setAcceptId(Integer.parseInt(acceptId));
            chat.setMsg(content);
            //消息已读
            chat.setReaded("y");
            chatService.add(chat);

            log.debug("接受方在线：发送方：{}，接收方：{}，消息：{}", sendId, acceptId, jsonObject.toString());
        } else {
            //（3）如果用户不在线，先判断一下，数据里面有没有这个接受方用户，
            // 如果有则，则将消息存到数据库中即可，不用吧消息在线发给接受方，等其登录后打开聊天框自己查询
            User acceptUser = userService.getUserById(Integer.parseInt(acceptId));
            if (acceptUser != null) {
                Chat chat = new Chat();
                chat.setSendId(Integer.parseInt(sendId));
                chat.setAcceptId(Integer.parseInt(acceptId));
                chat.setMsg(content);
                //消息未读
                chat.setReaded("n");
                chatService.add(chat);


                log.debug("接受方不在线：发送方：{}，接收方：{}，消息：{}", sendId, acceptId, chat);
            }

            log.debug("发送失败，接受方用户不存在", acceptId);
        }
    }

    @OnError
    public void onError(Session session, Throwable error) {
        log.debug("发生错误");
        error.printStackTrace();
    }

    /**
     * 服务端发送消息给客户端
     */
    private void sendMessage(String message, Session acceptSession) {
        try {
            log.debug("服务端给客户端发送消息{}", message);
            acceptSession.getBasicRemote().sendText(message);
        } catch (Exception e) {
            log.debug("服务端发送消息给客户端失败", e);
        }
    }

    /**
     * 服务端发送消息给所有客户端
     */
    private void sendAllMessage(String message) {
        try {
            for (Session session : sessionMap.values()) {
                log.info("服务端给客户端[{}]发送消息{}", session.getId(), message);
                session.getBasicRemote().sendText(message);
            }
        } catch (Exception e) {
            log.debug("服务端发送消息给客户端失败", e);
        }
    }
}
